#!/usr/bin/python
# -*- coding: utf-8 -*-


import re, sys, urllib, urllib2, os.path, logging, logging.handlers, hashlib, hmac, base64, urlparse
from time import localtime, strftime, sleep, time as nowtime
from math import radians, cos, sin, asin, sqrt
from heapq import heappush, heappop
import android
import fslib as fs
import dialog
import cPickle


#############################################
# configurazione: START #####################
#############################################

# directory e files
app_name = "Navigatore SL4A"
app_canonic_name = app_name.title().replace(" ", "")
private_dir = "/sdcard/sl4a/" + app_canonic_name
log_filename = os.path.join(private_dir, app_canonic_name + ".log")
# debug
log_filename = app_canonic_name + ".log"
# url pagina google api per calcolo distanza e localizzazione
url_gmap = "http://maps.googleapis.com/maps/api/distancematrix/json"
url_geocode = "http://maps.googleapis.com/maps/api/geocode/json"
url_direction = "http://maps.googleapis.com/maps/api/directions/json"
user_agent = 'Mozilla/5.0'
tolleranceStartDistance = 30  # raggio di cerchio ulteriore previsto dal punto di partenza
penalitaPieno = 10 #peso (minuti o km, o valore tra i due)
minCoveredDistance = 100  # distanza minima a cui devono trovarsi i collegamenti tra i benzinai
maxCoveredDistance = 200  # distanza massima ipotetica percorribile
distanceMatrixLimit = 90  # numero massimo di destinazioni con una sorgente
#offline = False
#timestamp, query distance matrix almeno ogni 10 secondi

  



# --- se non esiste crea directory privata dell'applicazione
if not os.path.isdir(private_dir):
   import os
   os.makedirs(private_dir)


# --- enable and initialize logging  
logger = logging.getLogger()  # root logger
handler = logging.handlers.RotatingFileHandler(log_filename, 'a', 16000, 2)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', "%H:%M:%S")
handler.setFormatter(formatter)
logger.addHandler(handler)
logLevel = logging.CRITICAL+1
logger.setLevel(logLevel) 
logging.getLogger('fslib').setLevel(logLevel)  # report level for fslib module 
logger.info("\n--- " + app_name + " started at %s ---" % strftime("%d-%b-%y %H:%M:%S", localtime()))

class ParseError(Exception):
  """Exception raised by the parsing of the web page"""
  pass

#############################################
# configurazione: END #######################
#############################################









#############################################
# 4display: START ###########################
#############################################

# classe contenente le etichette delle componenti 
# fondamentali (per avere le definizioni in un unico punto)
class Stringhe:
  #titolo = "Situazione ore %s"
  aggiorna = "Aggiorna"
  esci = "Esci"
  # localizzazione = "Localizzazione"
  # chiedi_indirizzo = "Scegli indirizzo dalla lista"
  ok = "Ok"
  annulla = "Annulla"
  nuovo = "Nuovo"
  scegli_addr = "Seleziona il luogo"
  linea_aria = "Calcolo distanze in linea d'aria"
  noEpSelected = "Per calcolare un itinerario occorre scegliere il luogo di partenza e di arrivo."
  
# classe con le definizioni delle dimensioni non testuali  

#############################################
# 4display: END #############################
#############################################














#############################################
# utility: START ############################
#############################################

def queryString(params):
    if params == "" or params == None or params == False or len(params) == 0:
        return ""
    else:
        return '&'.join(map(lambda k: k + "=" + urllib.quote_plus(str(params[k])), params.keys()))

def getFromWeb(url, params, googleAPI):  # params e' un dizionario
    try:
      query = queryString(params)
      logger.info("Richiesta Web: " + url + "?" + query + "\nParametri: " + str(params) + "\nEncoded Param: " + str(query))
      urlToRequest = url + "?" + query

      opener = urllib2.build_opener()
      opener.addheaders = [('User-agent', 'Mozilla/5.0'), ('GData-Version', '3.0')]
      text = opener.open(urlToRequest, "", 400).read()
      logger.info("Risultato Web: " + str(text))
    except urllib2.URLError:
      logger.exception("Errore urllib2 - URL: '" + url + "?" + query + "'")
      return None
    return text

def getGeoAddress(addr):  # ritorna tupla, i 2 valori di lat e lon o None
    text = getFromWeb(url_geocode, {"sensor": "false", "language":"it-IT", "address":"%s" % (addr) }, True)
    if text is None:
      return None
    text = eval(text, dict(true=True, false=False))
    if text["status"] != "OK":
        logger.exception("Geolocalizzazione per " + addr + " non trovata. Stato: " + text["status"])
        return None
    return text["results"][0]["geometry"]["location"]["lat"], text["results"][0]["geometry"]["location"]["lng"]

# in params ci sono solo origine (una) e destinazioni
def getMatrixDistance(params):  # restituisce una lista di tuple(secondi-metri)
    text = getFromWeb(url_gmap, dict(dict(language="it-IT", sensor="false"), **params), True)
    if text is None:
      return None
    text = eval(text, dict(true=True, false=False))
    if text["status"] != "OK":
        logger.exception("Distance Matrix fallita. Stato: " + text["status"])
        return None
    ret = []  # lista di coppie secondi,metri
    filter(lambda el: ret.append((el['duration']['value'], el['distance']['value'])), text['rows'][0]['elements'])  # crea una tupla(sec,mt) per ogni risultato
    return ret

def dumpData(myVar, filename):
    try:
        f = open(filename + ".dump", 'w')
        if f:
            cPickle.dump(myVar, f)
            f.close()
        else:
            logger.exception("Impossibile aprire il file su cui fare il dump")
    except IOError as e:
        logger.exception("Impossibile aprire il file su cui fare il dump: I/O error({0}): {1}".format(e.errno, e.strerror))

def getDump(filename):
    ret = None
    try:
        f = open(filename + ".dump", 'r')
        if f:
            ret = cPickle.load(f)
            f.close()
        else:
            logger.exception("Impossibile aprire il file da cui leggere il dump")
            return None
    except IOError as e:
        logger.exception("Impossibile aprire il file su cui fare il dump: I/O error({0}): {1}".format(e.errno, e.strerror))
    return ret


def haversine(lon1, lat1, lon2, lat2):
    """
    Calculate the great circle distance between two points 
    on the earth (specified in decimal degrees)
    """
    # convert decimal degrees to radians 
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
    # haversine formula 
    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
    c = 2 * asin(sqrt(a)) 
    km = 6367 * c
    return abs(km) 


#############################################
# utility: END ##############################
#############################################




#############################################
# classi: START #############################
#############################################

# classe che rappresenta una locazione geografica (citta' o stazione)
class Pos:
  nTrySolution = 2
  lastGoogleSearch = None
  startTime = None
  startDay = None
  
  all = {}  # dizionario[nome classe] = [ lista di istanze, istanze, istanze ]
  # la lista di istanze e' un dizionario nome-istanza
  
  @classmethod
  def appendElem(cls, elem):
    if cls.__name__ not in Pos.all:
      Pos.all[cls.__name__] = {}
    
    if not Pos.all[cls.__name__]:
      Pos.all[cls.__name__] = {}
    Pos.all[cls.__name__][elem.nome] = elem
    
  @classmethod
  def removeElem(cls, elem):
    if elem.nome in Pos.all[cls.__name__]:
        del Pos.all[cls.__name__][elem.nome]
        cls.dump()

  @classmethod
  def getAll(cls):
    if cls.__name__ not in Pos.all:
      return {}
    return Pos.all[cls.__name__] if Pos.all[cls.__name__] else {}#se None restituisce dizionario vuoto

  @classmethod
  def replaceElem(cls, elems):  # elems e' una lista
    cls.clear()
    Pos.all[cls.__name__] = elems

  @classmethod
  def clear(cls):
    if cls.__name__ not in Pos.all:
      Pos.all[cls.__name__] = {}
    else:
      Pos.all[cls.__name__].clear()  # elimina tutti i riferimenti, non i dati. Quelli che poi non sono utilizzati saranno eliminati dal garbadge collector 
  
  @classmethod
  def dump(cls):
    dumpData(Pos.all[cls.__name__], "dump" + cls.__name__)
  
  @classmethod  
  def load(cls):
    cls.replaceElem(getDump("dump" + cls.__name__))  # carica l'insieme delle istanze (rispettivamente di citta' o stazioni) direttamente nell'attributo statico della loro superclasse, accessibile da Classe.getAll()
    if Pos.all[cls.__name__] is None:
        Pos.all[cls.__name__] = {}
    
  def __init__(self, nome, lat, lon):
    self.nome = nome
    self.lat = lat
    self.lon = lon
    self.discovered = False  # e' stato utilizzato internet per ottenere i dati sui benzinai vicini
    self.linked = False  # # e' stata collegata alle altre stazioni vicina
    self.stationAvaiables = {}  # elenco di distributori raggiungibili da questo ## associa nomeStrazione - tupla(secondi,metri). ## Nota: non e' possibile inserire anche il riferimento alla classe della stazione perchÃ© poi al momento del dump va in loop in ricorsione
    
    self.appendElem(self)
  
  def distance(self, other):  # citta o anche un'altra stazione
    return haversine(other.lon, other.lat, self.lon, self.lat)
  
  def knows(self):
    self.links()
    if not self.discovered:
        all = Station.getAll()
        stations = self.stationAvaiables.keys()  # nomi stazioni
        stations = filter(lambda x: self.stationAvaiables[x] is None, stations)  # esclude quelle gia' richieste, (perchÃ© magari s e' gia' stata 'scoperta')
        
        while len(stations) > 0:
            Station.dump()
            logger.info("Sleeping...")
            logger.info("RIMANENTI " + str(len(stations)))
            
            sleepTime = None if Pos.lastGoogleSearch is None else int(nowtime()) - Pos.lastGoogleSearch
            
            if sleepTime != None:
                sleep(11-sleepTime)

            Pos.lastGoogleSearch = int(nowtime())
    
            if len(stations) >= distanceMatrixLimit:
                selStation = stations[:distanceMatrixLimit]
                del stations[:distanceMatrixLimit]
            else:
                selStation = stations[:]
                del stations[:]
            
            selCoords = map(lambda x: all[x].coords(), selStation)
            
            param = dict(origins=self.coords(), destinations='|'.join(selCoords))
            res = getMatrixDistance(param)
            if res is None:
                return False
            
            i = 0
            for r in res:
                self.stationAvaiables[selStation[i]] = r  # tupla(sec,mt)
                all[selStation[i]].stationAvaiables[self.nome] = r
                i += 1
        
        self.discovered = True
        self.dump()
    return True

  def __cmp__(self, other):  # per eventuali strutture dati ordinate per ricerca - per ora non capira perchÃ© ho cambiato idea
    if self is None and other is None:
      return 0
    elif self is None:
      return 1
    elif other is None:
      return -1
    elif self.lat == other.lat and self.lon == other.lon:
      return 0
    elif self.lat < other.lat:
      return -1
    elif self.lat > other.lat:
      return 1
    return 1 if self.lon > other.lon else -1  # ternaria
  
  def __str__(self):
    return self.nome

  def coords(self):
    return "%f,%f" % (self.lat, self.lon)

  # usato solo per debugging  
  def __repr__(self):
    return  "%s (%s)" % (Pos.__str__(self), self.coords())


class Address(Pos):
  
  sourceSelected = None
  destinationSelected = None
  
  # Address.dump()
  # Address.load()
  # Address.appendElem(el)
  # Address.getAll()
  
  @staticmethod
  def getAddressByWeb(nome, addr):
    # serve web, flag offline(?)
    res = getGeoAddress(addr)
    if res == None:
      return None
    c = Address(nome, res[0], res[1], addr)
    c.knows()  # fa il link ed interroga google per sapere delle stazioni vicine
    Address.dump()  # salvare o non salvare(?)

    return c

  def __init__(self, n, lat, lon, address_text):
    self.address_text = address_text
    
    Pos.__init__(self, n, lat, lon)

  # restituisce una lista di 2 coppie di punti: gli step del percorso
  def getDirection(self, other_addr):
    params = { "sensor":"false", "language":"it-IT", "origin":self.coords(), "destination":other_addr.coords() }
    direction = eval(getFromWeb(url_direction, params, True))
    if direction["status"] != "OK":
        logger.exception("Percorso per " + other_addr.address_text + " non trovato. Stato: " + text["status"])
        return None
    
    res = []
    start = [ self.lat, self.lon ]
    
    for step in direction["routes"][0]["legs"][0]["steps"]:
        end = [ step["end_location"]["lat"], step["end_location"]["lng"] ]
        res.append(start + end)
        start = end
    return res
  
  def links(self):
    if not self.linked:
        for s in Station.getAll().itervalues():
          if self.distance(s) <= tolleranceStartDistance:
            self.stationAvaiables[s.nome] = None  # none perchÃ© non si sa ancora la distanza (deve essere richiamato il web)
        self.linked = True
  
  def __str__(self):
    return "Citta: %s" % Pos.__str__(self)

  # usato solo per debugging  
  def __repr__(self):
    return  Pos.__str__(self)
  

# classe che rappresenta una singola stazione (di benzina :) 
class Station(Pos):
    
  # Station.dump()
  # Station.load()
  # Station.appendElem(el)
  # Station.getAll()
  
  # output: dizionario(nome_stazione:oggetto)
  @staticmethod
  def haversinCut(cityA, cityB):
      all = Station.getAll()
      res = {}
      
      # sicuramente tra le stazioni considerate, ci saranno quelle raggiungibili dall'origine e dalla destazione
      for stationName in set(cityA.stationAvaiables.keys() + cityB.stationAvaiables.keys()):  # evitando duplicati
          res[stationName] = all[stationName]
          del all[stationName]
      
      addressDistance = cityA.distance(cityB)  # haversine(cityA.lon, cityB.lat, cityB.lon, cityB.lat)
      for stationName, station in all.iteritems():  # O(n)
          distanceA = station.distance(cityA)
          distanceB = station.distance(cityB)
          # se la stazione si trova dentro il "cerchio" tracciato partendo dalla citta d'origine o destinazione
          # se cade nell'intersezione dei due cerchi
          # (il cerchio ha raggio = distanza tra le due citta')
          if distanceA <= addressDistance and distanceB <= addressDistance:
              res[stationName] = station
      
      return res


  @staticmethod
  def myCut(cityA, cityB, tolleranceHighway, tolleranceKm):
      # all = Station.getAll().values()
      all = Station.haversinCut(cityA, cityB)
      res = {}
      
      # sicuramente tra le stazioni considerate, ci saranno quelle raggiungibili dall'origine e dalla destazione
      for stationName in set(cityA.stationAvaiables.keys() + cityB.stationAvaiables.keys()):  # evitando duplicati
          res[stationName] = all[stationName]
          del all[stationName]
      
      steps = cityA.getDirection(cityB)
      if steps is None:
          return None
      
      #f = open('step.csv', 'w')
      #for step in steps:
          f.write("%s;%s;\n%s;%s\n\n" % (str(step[1]), str(step[0]), str(step[3]), str(step[2])))
      #f.write("\n\n\n")
      
      steps = steps[1:-1]  # si escludono origine e destinazione
      
      for step in steps:  # [ lat, lon, lat, lon ]
          temp = all.copy()  # all non si puo' modificare durante il ciclo
          od = haversine(step[1], step[0], step[3], step[2])  # origine - destinazione
          
          for stationName, station in all.iteritems():
              op = haversine(station.lon, station.lat, step[1], step[0])  # origine - punto
              dp = haversine(station.lon, station.lat, step[3], step[2])  # destinazione - punto
              if dp <= tolleranceHighway:
                  if op <= od and dp <= od:
                      res[stationName] = station
                      del temp[stationName]
              else:
                  # Piu' e' lungo il tratto in linea d'aria e piu' probabilita' c'e' che la strada non sia dritta. Quindi la tolleranza non e' fissa, ma aumenta in relazione alla distanza in linea d'aria
                  if op + dp <= (od + tolleranceKm) * 1.1:
                      res[stationName] = station
                      del temp[stationName]
          all = temp.copy()
    
              
      return res          
  
            
  # #->e' in questa funzione che vengono prelevati i dati da internet per le stazioni
  @staticmethod
  def knowsAll(all):  # per ogni stazione, guarda la lista delle sue collegate e preleva i dati dal web
    # debug
    if all == None:
        all = Station.getAll()
    # ##
    i = 0
    for station in all.itervalues():
        logger.info("\tRIMANENTI: " + str(len(all) - i))
        station.knows()
        i += 1

  @staticmethod
  def linksAll(all):  # da chiamare dopo il parsing ## Da ogni stazione vede quali altre e' possibile raggiungere e lo scrive
    # debug
    if all == None:
        all = Station.getAll()
    # ##
    for station in all.itervalues():  # O(n2)
        station.links()


  def __init__(self, n, lat, lon):
    # stationAvaiables ha come key il nome della stazione, si e' testato che (dopo aver impostato un hash) l'istanza come key rallenta - Inoltre poi sarebbe impossibile fare il dump
    self.indirizzo = self.prezzo = self.orari = None 
    
    Pos.__init__(self, n, lat, lon)
  

  def links(self):
    if not self.linked:  # flag, gia fatto
      all = Station.getAll()
      for station in all.itervalues():
        if self != station and station.nome not in self.stationAvaiables.keys():  # si assume che se non c'e' il collegamento qui c'e' anche lÃ¬ e viceversa
            dist = self.distance(station)  # haversine
            
            # stationAvaiables non contiene un riferimento all'stanza perchÃ© altrimenti nel dump va in loop in ricorsione
            if dist <= maxCoveredDistance and dist >= minCoveredDistance:
                self.stationAvaiables[station.nome] = None  # il resto (km, minuti) verra' aggiunto se necessario dal web
                station.stationAvaiables[self.nome] = None
      self.linked = True
        
  def update(self, details):  # details e' un dizionario
    for k, v in details.iteritems():
      setattr(self, k, v)  # genera il nome degli attributi nella classe con il nome contenuto nella key del dizionario
    
  def __str__(self):
    return "Benzinaio: %s" % Pos.__str__(self)

  # usato solo per debugging  
  def __repr__(self):
    return  "{ 'nome':%s, 'lat':%f, 'lon':%f, 'discovered': %s, 'stationAvaiables':%s, 'orari':%s, 'indirizzo':%s, 'linked':%s, 'prezzo':%s }" % \
        (repr(self.nome), self.lat, self.lon, str(self.discovered), str(self.stationAvaiables), str(self.orari), repr(self.indirizzo), str(self.linked), str(self.prezzo))


  

#############################################
# classi: END ###############################
#############################################













#############################################
# soluzioni viaggio: START ##################
#############################################

# tenta di ottenere soluzioni in modo piu' veloce, se non ci riesce va ad un altro metodo
def giveMeSolutions(cityA, cityB, bestTime):
  if not cityA.knows() or not cityB.knows():
      logger.error("Non e' stato possibile chiedere a google le distanze per le stazioni vicino della citta' " + str(cityA) + " o " + str(cityB))
      return None, True
  else:
      for mode in ['my1', 'my2', 'haversinepuro(?)']:
        res = travel(cityA, cityB, mode, bestTime)
        if res is not None:
            return res, True#secondo res e' inutile
        logger.warning('Soluzione tentata con ' + mode + ' fallita')
  return None, False

# restituisce None o array( [percorso migliore], secondi, metri )
def travel(cityA, cityB, mode, bestTime):
  # #prima si sfoltiscono
  # #poi solo per quelle rimanenti si vedono quali stazioni vedono quali, se necessario
  # #poi si cicla un ultima volta (la complessita' non aumenta, si preferisce tenere la funz separata) per ottenere i dati dal web sulla distanza, se necessari
   
  Station.load()  # reload
  
  if mode == 'my1':
      res = Station.myCut(cityA, cityB, 10, 10)
  elif mode == 'my2':
      res = Station.myCut(cityA, cityB, 50, 60)
  else:
      res = Station.haversinCut(cityA, cityB)

  Station.replaceElem(res)
  
  resumeDb()  # preleva tempi e distanza verso stazioni, orari apertura, indirizzo, prezzo per ogni stazione
  
  # shortest path
  solutions = {}  # #secondi+17*metri: array di ([percorso migliore], secondi, metri)
    # (secondi+17*metri) vorrebbe essere un hash della soluzione
  
  if bestTime is None:
      # es con nTrySolution = 4, il termometro varra': 0, 0.25, 0.50, 0.75, 1
      for i in range(Pos.nTrySolution):
          new_solution = shortestPath(cityA, cityB, 1.0 * i / (Pos.nTrySolution-1))
          if new_solution is not None:
              # e' inverosimile che 2 soluzioni abbiano esattamente lo stesso tempo si percorrenza e gli stessi km
              solutions[new_solution[1] + 17 * new_solution[2]] = new_solution  # se esiste gia' (si suppone sia uguale) viene sovrascritta, altrimenti no. In conclusione non ci sono doppioni
  else:
      new_solution = shortestPath(cityA, cityB, 1 if bestTime else 0)
      if new_solution is not None:
          solutions[0] = new_solution
      
  
  return solutions.values() if len(solutions) > 0 else None

# start e end sono Address
# restituisce None o ( [percorso migliore], secondi, metri ) 
def shortestPath(start, end, thermometer):
    inf = sys.maxint  # infinito
    stations = Station.getAll()
    
    if Pos.startTime is None:
        now = localtime()
        Pos.startTime = now.tm_hour * 60 * 60 + now.tm_min * 60 + now.tm_sec  # numero di secondi dalla mezzanotte
        Pos.startDay = now.tm_wday
        
    class Node:
        source = None
        destination = None
        therm = thermometer  # numero tra 0 e 1. che da importanza (1) al tempo o (0) alla distana fisica
        all = {}
        
        def __init__(self, pos):
            self.timeTravel = None  # tempo accumulato dalla sorgente, per vedere gli orari. Si usa modulo 24*60*60 (non e' doc who)
            self.metTravel = None  # metri accumulati dalla sorgente
            self.prev = None  # predecessore per il percorso dalla sorgente
            self.pos = pos  # da cui si ricavano i dati tra cui gli archi e gli orari e si puo' vedere se e' origine o destinazione
            self.dist = inf  # distanza dalla sorgente
            self.all[pos.nome] = self
        
        def distance_general(self, other, mode):
            if other.pos.nome not in self.pos.stationAvaiables.keys():
                if other is self.destination:
                    return self.destination.distance(self)
                logger.warning("Lo shortest path ha cercato la distanza tra il nodo " + str(self.pos) + " ed il nodo " + str(other.pos) + " che non sono collegati")
                return inf  # non dovrebbe mai accadere
            else:
                # link e' tupla di secondi, metri
                link = self.pos.stationAvaiables[other.pos.nome]
                if mode is 2: #costo dijkstra
                    return self.therm * link[0] + (1 - self.therm) * link[1]
                elif mode is 1: #mt
                    return link[1]
                else: #sec
                    return link[0]
        
        # e' il costo per l'algoritmo
        def distance(self, other):
            return self.distance_general(other,2);
            
        def distanceMt(self, other):
            return self.distance_general(other,1);
            
        def distanceSec(self, other):
            return self.distance_general(other,0);
        
        # dato un predecessore gia' 'scoperto', si vede se l'arco puo' esistere o no, per il tempo e gli orari. Si guardano gli orari di apertura
        def isLinked(self, prev):
            if prev == self.source or prev == self.destination or self == self.destination:  # origine, punto di partenza del viaggio, non ha gli orari
                return True
            elif prev.pos.nome not in self.pos.stationAvaiables.keys():
                logger.warning("Lo shortest path ha cercato di vedere se c'era un collegamento tra " + str(self.pos) + " ed il nodo " + str(prev) + " che non sono collegati")
                return False  # non dovrebbe mai accadere
            elif prev.timeTravel == None:
                logger.warning("Lo shortest path ha cercato di vedere se c'era un collegamento tra " + str(self.pos) + " ed il nodo " + str(prev) + " e l'ultimo non e' stato collegato alla sorgente")
                return False  # non dovrebbe mai accadere
            else:
                secFromSource = Pos.startTime + prev.timeTravel + self.pos.stationAvaiables[prev.pos.nome][0]  # secondi
                
                gg = secFromSource / 60 / 60  # #divisione tra interi, va benissimo
                dayFromSource = gg + Pos.startDay

                # localtime: tm_wday     range [0, 6], Monday is 0 - http://docs.python.org/2/library/time.html#time.struct_time                
                if dayFromSource == 5:  # sabato = prefestivo
                    orariText = "Prefestivi"
                elif dayFromSource == 6:  # domenica = festivo
                    orariText = "Festivi"
                else:
                    orariText = "Feriali"
                
                secDay = secFromSource % (24 * 60 * 60)
                for pairTime in self.pos.orari[orariText]:
                    if secDay > pairTime[0] and secDay < pairTime[1]:
                        return True
                
                return False
    
    
    class AStarNode(Node):
        def __init__(self, pos):
            Node.__init__(self, pos)
            self.visited = None#ne opened ne close, se True Ã¨ closed, se False Ã¨ opened
            self.heuristicDistance = None #se calcolata la si salva
            
        def heuristicCalc(self):
            if self.heuristicDistance is None:
                self.heuristicDistance = self.pos.distance(end) * 1000#km
            #130000/60/60 = 130 km/h in formato m/s
            return self.therm * self.heuristicDistance * 1300/36 + (1 - self.therm) * self.heuristicDistance
            
        def estimatedCost(self):
            return self.dist if self.dist is inf else self.dist + self.heuristicCalc()
        
        # per l'heap
        def __cmp__(self, other):
           return cmp(self.estimatedCost(), other.estimatedCost())
    
    
    algNode = AStarNode
        
        
    # la classe Node e' finita, ora inizia l'algoritmo
    algNode.source = algNode(start)
    algNode.destination = algNode(end)
    for station in stations.itervalues():
        algNode(station)
        
    algNode.source.dist = 0
    algNode.source.timeTravel = 0
    algNode.source.metTravel = 0
    Q = [algNode.source]  # coda di priorita', sarÃ  ordinato in base al tipo di algNode, perchÃ© hanno cmp diversi
    
    # la sorgente e' collegata alle sue stazioni vicine (ed anche se non c'e' l'arco nel senso opposto, va bene)
    # mentre invece la destinazione e' un problema: anzichÃ© aggiungere archi agli oggetti stazioni si crea una condizione apposta
    nearDestination = set(end.stationAvaiables.keys())  # con il set la ricerca (spero) e' migliore
    
    while len(Q) > 0:
        u = heappop(Q)
        
        if u.pos == algNode.destination.pos:
            break
        
        nearU = u.pos.stationAvaiables.keys()
        if u.pos.nome in nearDestination:  # si aggiunge l'arco con la destinazione
            nearU += [ end.nome ]
        
        u.visited = True
        for vStr in nearU:  # vPos e' la stringa del nodo collegato
            if vStr not in algNode.all:  # non si considerano tutte quelle collegate, ma tutte quelle collegate non escluse prima di questa funzione, cioe' getAll non contiene tutte le stazioni, anche se c'e' un riferimento (stringa) tra quelle collegate di alcune prelevate con getAll
                continue
            
            v = algNode.all[vStr]
        
            if v.isLinked(u):
                vPos = v.pos
                distSUV = u.dist + u.distance(v)
                
                thisEstimate = distSUV + v.heuristicCalc()
                if v.visited and thisEstimate >= v.estimatedCost():
                    continue
                if v.visited is not False or thisEstimate < v.estimatedCost():
                    v.timeTravel = u.timeTravel + u.distanceSec(v)  # secondi
                    v.metTravel = u.metTravel + u.distanceMt(v)  # metri
                    v.dist = distSUV
                    v.prev = u
                    if v.visited is not False:
                        v.visited = False
                        heappush(Q, v)

            
    # fine. ora reverse della soluzione per ottenerla in ordine
    if algNode.destination.prev is None:
        logger.warning("Non e' stata trovata alcuna soluzione.")
        del Node.all
        return None  # destinazione e sorgente non collegati
    res = []
    node = algNode.destination
    while node.prev.pos is not start:
        res.append(node.pos)
        node = node.prev
    res.append(start)
    
    res.reverse()
    del Node.all
    return (res, algNode.destination.timeTravel, algNode.destination.metTravel)

def str_distanza_fisica(metri):
    if metri<1000:
      dis = "%dm" % metri
    elif metri<10000:
      dis = "%.1fkm" % (1.0*metri/1000)
    else:
      dis = "%dkm" % int(metri/1000)
    return dis

def str_distanza_oraria(secondi):
    if secondi<60:
      dis = "%d''" % secondi
    elif secondi<60*60:
      dis = "%d'" % int(1.0*secondi / 60 + 0.5)
    else:
      ore = int(1.0*secondi / (60*60))
      min = int(1.0*(secondi - ore*60*60)/ 60 + 0.5)
      dis = "%d h e %d'" % (ore, min)
    return dis

#############################################
# soluzioni viaggio: END ####################
#############################################







#############################################
# parser: START #############################
#############################################

def dumpDb():
    all = Station.getAll()
    try:
        f = open('all.db', 'w')
        if f:
            for s in all.keys():
                f.write("%s:%s\n" % (s, repr(all[s])))
            f.close()
        else:
            logger.exception("impossibile creare il file all.db")
    except IOError as e:
        logger.exception("Impossibile aprire il file all.db: I/O error({0}): {1}".format(e.errno, e.strerror))

# funzione che legge all.db e per ogni stazione presente in Station.getAll() carica i dettagli
def resumeDb():
    all = Station.getAll()
    n = len(all)

    try:
        f = open('all.db', 'r')
        if f:
            for line in f:
                if line[-1] == '\n':
                    line = line[0:-1]  # elimina \n
        
                s = line.split(':', 1)  # nome:{dati_key:dati_value, ...}
                if s[0] in all:  # la chiave e' presente
                    all[s[0]].update(eval(s[1]))
                    n -= 1
                    
                if n is 0:
                    break
                
            f.close()
        else:
            logger.warning("file all.db inesistente")
    except IOError as e:
        logger.exception("Impossibile aprire il file all.db: I/O error({0}): {1}".format(e.errno, e.strerror))


# converte la lista generata nella funzione sotto da stringhe di HH:SS a secondi
def timeExtractAndConvert(words):
    resText = []
    timeExtract(words, resText)
    res = []
    for pair in resText:
        addPair = []
        for time in pair:
            t = time.split(':')
            addPair.append(int(t[0]) * 60 * 60 + int(t[1]) * 60)
        res.append(addPair)
    return res
    

# passato un array words come input ed un array ret come output
def timeExtract(words, ret):  # si crea una lista di coppie di orari, possono essere ad orario continuato o fare pausa pranzo.
    if len(words) == 0:
        return ret
    
    if words[0] == 'chiuso':
        del words[0]
    elif words[0] == '-':
        del words[0:2]
    elif words[1] == 'chiuso' or words[1] == '-':
        logger.exception('Errore parsing: trovato chiuso e non una coppia di orari')
    else:
        ret.append(words[0:2])  # copia 
        del words[0:2]
        timeExtract(words, ret)
    return ret


# non funziona bene pero'
# os.system("ps2ascii Elenco_Metano_Italy_20130918.pdf Elenco_Metano_Italy_20130918.pdf.txt")

def parseGeoStation():  # chiama il costruttore Station e dopo li si possono ottenere da Station.getAll()
    Station.clear()
    f = open("EcomotoriMetanoIta.csv", "r")
    for line in f:
        words = line.split(';')
        
        Station(words[0], float(words[1]), float(words[2]))
    f.close()
    Station.dump()

# carica i dettagli delle stazioni in memoria, dal file elenco_metano.txt
def parseDetailsStations():    
    distributori = Station.getAll()
    distributoriNames = distributori.keys()
    
    f = open("elenco_metano.txt", "r")
    
    lastDistributore = False
    for line in f:
        line = line[0:-1]  # elimina \n
        if len(line) == 0:  # linea vuota
            continue;
        if line[-1] == '\r':
            line = line[0:-1]  # elimina \r
        if len(line) == 0:  # linea vuota
            continue;
    
        words = filter(None, line.split(' '))  # rimuove le stringhe vuote
        # ignora linee inutili
        if words[0] in ['ELENCO', 'Casello', 'Distributore', 'aggiornato' , 'Dati', 'Tutte', 'pertanto'] \
        or (words[0] == 'Orario' and words[1] == 'Solare'): 
            continue
        # formato: "id" "-" "indirizzo da pi word" "Prezzo:" "0.xyz" "Eur"
         
        if words[0] == "Orario":
            if lastDistributore == False:  # il distributore e' chiuso o comunque (flag) si vuole saltare
                continue
            words = words[4:]
            orari = {}
            for label in ["Feriali", "Prefestivi", "Festivi"]:
                orari[label] = timeExtractAndConvert(words)
            distributori[lastDistributore].update({"orari": orari})
        else:
            if words[-1] == "Chiuso" or words[0] not in distributoriNames:
                lastDistributore = False  # flag per saltare il prossimo ciclo
                continue
            else:
                lastDistributore = words[0]  # nome
            distributori[lastDistributore].update({ "indirizzo": ' '.join(words[2:words.index('Prezzo:')]) , 'prezzo':words[words.index('Prezzo:') + 1]})
    
    f.close()


#############################################
# parser: END ###############################
#############################################





#######SL4A

def ottieni_GPS(droid, attesa):
    droid.startLocating() # Chiamata al main
    logger.info("Avvio GPS")
    res = None
    event = droid.eventWaitFor('location',attesa).result
    
    if event is not None and event['name'] == "location":
      try:
        lat = str(event['data']['gps']['latitude'])
        lon = str(event['data']['gps']['longitude'])
      except KeyError:
        lat = str(event['data']['network']['latitude'])
        lon = str(event['data']['network']['longitude'])    
      res = (lat, lon)
    droid.stopLocating() # Chiamata al main
    return res

def nuovo_GPS(droid, newCityName):
    logger.info("Ricerca luogo GPS: "+newCityName)
    
    droid.dialogCreateSpinnerProgress(app_name, "Rilevamento posizione GPS...")
    droid.dialogShow()
    
    gps = ottieni_GPS(droid, 10000)
    droid.dialogDismiss()
    if gps is None:
        dialog.alert(droid,app_name,"Non e' stato possibile rilevare la posizione tramite GPS",[Stringhe.ok])
        return None
    else:
        dialog.alert(droid,app_name,"Il luogo '%s' e' stato aggiunto correttamente." %(newCityName),[Stringhe.ok])
        return Address(newCityName, float(gps[0]), float(gps[1]), '')

def nuova_citta(droid):
    newCityName, i = dialog.text_input(droid,app_name,"Inserisci il nome del luogo",
                                ['Procedi','Annulla'])
    if i == 0 and newCityName != "":
      newCityAddr, i = dialog.text_input(droid,app_name,"Inserisci l'indirizzo del luogo",
                             ['Fatto', 'Rileva posizione tramite GPS','Annulla'])
      if i == 0 and newCityAddr != "":
          logger.info("Ricerca nuovo luogo: "+newCityName + " " + newCityAddr)
          
          droid.dialogCreateSpinnerProgress(app_name, "Ricerca in corso...")#su google
          droid.dialogShow()
          
          cities = Address.getAll()
          
          newCityObj = Address.getAddressByWeb(newCityName, newCityAddr)
          if newCityObj is not None:
              newCityObj.knows()
          
          droid.dialogDismiss()
          
          if newCityObj is None:
            logger.info("Ricerca fallita")
            dialog.alert(droid,app_name,"Non e' stato possibile trovare l'indirizzo '%s'" %(newCityAddr),[Stringhe.ok])
          else:
            dialog.alert(droid,app_name,"Il luogo '%s' e' stato aggiunto correttamente." %(newCityName),[Stringhe.ok])
            return newCityObj
      elif i == 1:
          return nuovo_GPS(droid, newCityName)
    return None

def seleziona_citta(droid, exclude, canNew):
  """Show dialog to select a new city"""
  """ritorna citta e booleano che indica se e' nuova"""
  
  # i possibilil luogi gia' registrati
  allAddr = Address.getAll().copy()
  if exclude != None and exclude.nome in allAddr:
        del allAddr[exclude.nome]
      
  opt = [Stringhe.annulla]  
  if canNew:
      opt = ["Inserisci nuovo luogo", Stringhe.annulla]
    
  if len(allAddr) > 0:
    items = sorted(allAddr.keys())
    sel,i = dialog.single_choice_list(droid,Stringhe.scegli_addr,items,opt)
    if sel:
      return allAddr[items[i]], False
    elif i==0 and canNew:#tasto "Nuovo" 
      return nuova_citta(droid), True
  elif not canNew:
    i = dialog.alert(droid,app_name,"Non sono presenti luogi memorizzati",["Inserisci nuovo luogo", "Torna al menu'"])
    if i==0:#tasto "Nuovo" 
      return nuova_citta(droid), True
  return None, False

def seleziona_itinerario(droid, bestTime):
    solutions, found = giveMeSolutions(Address.sourceSelected, Address.destinationSelected, bestTime)
    if solutions is None:
        droid.dialogDismiss()
        if found:
            dialog.alert(droid,app_name,"Non e' stato possibile trovare le stazioni vicine al luogo di partenza o di destinazione",[Stringhe.ok])
        else:
            dialog.alert(droid,app_name,"Non e' stata trovata alcuna soluzione",[Stringhe.ok])
        return None, False
    else:
        #solutions=array(array( [percorso migliore], secondi, metri )
        if len(solutions)==1:
            return solutions[0][0], True
        elif bestTime is not None:
            res = solutions[0]
            discriminante = 1 if bestTime else 2
            for sol in solutions:
                if sol[discriminante] < res[discriminante]:
                    res = sol
            return res[0], False
        else:
            items = []
            
            for sol in solutions:
                label = "%s - Distanza %s" %(str_distanza_oraria(sol[1]), str_distanza_fisica(sol[2]))
                items.append([sol[1], label, sol[0]])
            
            items = sorted(items, lambda x,y: cmp(x[0],y[0]))
            
            labels = map(lambda x: x[1],items)
            
            sel,i = dialog.single_choice_list(droid,"Scegli un itinerario",labels,
                                    [Stringhe.annulla])
            if sel:
                return items[i][2], True
    return None, True
    
    
def my_main():
    droid = android.Android()
    #droid.startLocating()
    
    droid.dialogCreateSpinnerProgress(app_name, "Caricamento dati...")
    droid.dialogShow()
    Station.load()
    Address.load()
    
    cities = Address.getAll()
    droid.dialogDismiss()
    
    event_loop(droid, cities)
    #droid.stopLocating()


def event_loop(droid,clist):
  allAddr = Address.getAll().keys()
  
  while True:
    
    startName = "" if Address.sourceSelected is None else " :: " + Address.sourceSelected.nome
    endName = "" if Address.destinationSelected is None else " :: " + Address.destinationSelected.nome
    opt = [ "Seleziona partenza" + startName, "Seleziona destinazione" + endName, "Imposta ora e data di partenza", "Elimina Luogo", "Calcola itinerario" ]

    sel,i = dialog.single_choice_list(droid,app_name,opt,
                                    ["Chiudi"])

    if sel:
        if i==0:#"Seleziona partenza"
            startCity, ignore = seleziona_citta(droid, None, True)
            if startCity is not None:
              if Address.destinationSelected == startCity:
                  Address.destinationSelected = None
              Address.sourceSelected = startCity
              logger.info("Luogo di partenza selezionato: "+startCity.nome)
        elif i==1:#"Seleziona destinazione":
            endCity, ignore = seleziona_citta(droid, Address.sourceSelected, True)
            if endCity:
              if Address.sourceSelected == endCity:
                  Address.sourceSelected = None
              Address.destinationSelected = endCity
              logger.info("Luogo di destinazione selezionato: "+endCity.nome)
        elif i==2:#"Imposta ora e data di partenza"
            res = dialog.selDayTime(droid)
            if res is not None:
                Pos.startTime = res[0]
                Pos.startDay = res[1]
        elif i==3:#"Elimina Luogo"
            deleteCity, isNewCity = seleziona_citta(droid, None, False)
            if deleteCity and not isNewCity:
                if Address.sourceSelected == deleteCity:
                  Address.sourceSelected = None
                elif Address.destinationSelected == deleteCity:
                  Address.destinationSelected = None
                Address.removeElem(deleteCity)
        elif i==4:#"Calcola itinerario"
            
            if Address.sourceSelected is None and Address.destinationSelected is None:
                dialog.alert(droid,app_name,"Occorre definire il luogo di partenza e di destinazione",[Stringhe.ok])
            elif Address.sourceSelected is None:
                dialog.alert(droid,app_name,"Occorre definire il luogo di partenza",[Stringhe.ok])
            elif Address.destinationSelected is None:
                dialog.alert(droid,app_name,"Occorre definire il luogo di destinazione",[Stringhe.ok])
            elif Address.sourceSelected==Address.destinationSelected:
                dialog.alert(droid,app_name,"Occorre definire un luogo di destinazione diverso da quello di partenza",[Stringhe.ok])
                Address.destinationSelected = None
            else:
                sel,i = dialog.single_choice_list(droid,"Indica quante soluzioni diverse vorresti",range(1,6),
                                      ["La piu' veloce", "La piu' corta", Stringhe.annulla])
                if not sel and (i == 2 or i==-1):
                    continue

                if sel:
                    Pos.nTrySolution = i+1
                    bestTime = None
                else:
                    bestTime = i is 0
                
                droid.dialogCreateSpinnerProgress(app_name, "Calcolo itinerario...")#su google
                droid.dialogShow()
              
                sol, dismiss = seleziona_itinerario(droid, bestTime)
                
                if sol is None:
                    if dismiss:
                        droid.dialogDismiss()
                else:
                    desc = "Distributori lungo il tragitto:"
                    i = 0
                    url = "https://maps.google.com/maps?saddr=" + sol[0].coords()
                    for s in range(1,len(sol)-1):
                        url += " to:" + sol[s].coords()
                        i+=1
                        desc+="\n%d: %s - %s" % (i, sol[s].nome, sol[s].indirizzo)
                    url+= "&daddr=" + sol[len(sol)-1].coords()
                    
                    logger.info("activity per: %s" % url)
                    
                    wait = False
                    activity    = 'android.intent.action.VIEW'
                    packagename = 'com.google.android.apps.maps'
                    classname   = 'com.google.android.maps.MapsActivity'
                    intent      = droid.makeIntent(activity, url, None, None, None, packagename, classname).result
                    
                    droid.dialogDismiss()
                    droid.notify(app_name, "E' stato avviato il navigatore")
                    droid.startActivityIntent(intent, wait)
                    sleep(10)
                    dialog.alert(droid, app_name, desc, ["Chiudi"])
                    return
        else:
            return
    else:
        return
    
if __name__ == '__main__':    
    my_main()
